package com.onpositive.knowledge.model.inference;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

import com.onpositive.data.IPropertyConstants;
import com.onpositive.data.units.Unit;
import com.onpositive.data.units.UnitKind;
import com.onpositive.data.units.Units;
import com.onpositive.dataminer.core.pmodel.AliasProvider;
import com.onpositive.knowledge.model.IThing;
import com.onpositive.knowledge.model.impl.BasicUniverseProperty;
import com.onpositive.knowledge.model.values.DescribedValue;
import com.onpositive.knowledge.model.values.IHasValue;
import com.onpositive.knowledge.model.values.IValueTransformer;
import com.onpositive.semantic.model.api.access.IClassResolver;
import com.onpositive.semantic.model.api.expressions.ConstantExpression;
import com.onpositive.semantic.model.api.expressions.IExpressionEnvironment;
import com.onpositive.semantic.model.api.expressions.IListenableExpression;
import com.onpositive.semantic.model.api.meta.DefaultMetaKeys;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.semantic.model.expressions.impl.ExpressionParserV2;
import com.onpositive.units.ParsedDimension;
import com.onpositive.units.ParsedIdent;
import com.onpositive.units.ParsedValueSequence;

public class InferTransformer implements IValueTransformer {

	private static final long serialVersionUID = -1690038827808716879L;

	public static final String ID = "infer-transfotmer";

	ThreadLocal<HashSet<IProperty>> mm = new ThreadLocal<HashSet<IProperty>>();

	@Override
	public DescribedValue[] adjust(final Object object, IProperty prop, DescribedValue... original) {
		
		String[] potentialInferenceRoutes = AliasProvider.getPotentialInferenceRoutes(prop.getId());
		if (object instanceof IThing) {
			for (String s : potentialInferenceRoutes) {
				IThing sm = (IThing) object;
				IProperty property = sm.getProprty(s);
				if (property != null) {
					Object value = property.getValue(sm);
					if (value != null) {
						if (value instanceof DescribedValue) {
							return new DescribedValue[] { (DescribedValue) value };
						}
						if (value instanceof DescribedValue[]) {
							return (DescribedValue[]) value;
						}
					}
				}
			}
		}

		HashSet<IProperty> hashSet = mm.get();		
		if (hashSet == null) {
			hashSet = new HashSet<IProperty>();
			mm.set(hashSet);
		}
		hashSet.clear();

		if (hashSet.contains(prop)) {
			return new DescribedValue[0];
		}
		try {
			hashSet.add(prop);
			final BasicUniverseProperty bm = (BasicUniverseProperty) prop;

			boolean b = true;//original == null || original.length == 0;
			if (!b) {
				boolean nn = true;
				l2: for (IHasValue c : original) {
					while (c instanceof IHasValue) {
						Object value = ((IHasValue) c).value();
						if (value instanceof IHasValue) {
							c = (IHasValue) value;
						} else {
							if (value != null) {
								nn = false;
								break l2;
							} else {
								break;
							}
						}
					}
				}
				b |= nn;
			}
			ArrayList<DescribedValue> dvList = new ArrayList<DescribedValue>(Arrays.asList(original));
			if (b)
			{
				String[] value = DefaultMetaKeys.getValue(prop,	InferenceUtils.EXPRESSION_TO_INFER, String[].class);
				if (value != null)
				{
					OwnExpressionEnvironment ee = new OwnExpressionEnvironment(object, bm);
					for (String s : value)
					{						
						s = replaceSpacesButInLiterals(s);
						IListenableExpression<?> parse = new ExpressionParserV2().parse(s, ee, null);

						Object calculate = parse != null ? parse.getValue()	: null;
						if (calculate != null) {
							Object[] arr = null;
							
							
							if (calculate.getClass().isArray()){
								arr = (Object[]) calculate;
							}
							else{
								arr = new Object[]{calculate};
							}
							for(Object o : arr){
								Object o1 = adjustValue(o, prop);
								dvList.add(new DescribedValue( true, o1, s,	getId()));
							}
						}
					}
				}
				Object dv = DefaultMetaKeys.getValue(prop, InferenceUtils.DEFAULT_VALUE, Object.class);
				if (dv != null) {
					if (dv instanceof DescribedValue) {
						dvList.add( new DescribedValue(true,((DescribedValue) dv).value()) );
					}
					else{
						dvList.add( new DescribedValue(true, dv) );
					}
				}
			}
			return dvList.toArray( new DescribedValue[dvList.size()]);

		} finally {
			hashSet.remove(prop);
		}
	}

	private String replaceSpacesButInLiterals(String s) {
		StringBuilder bld = new StringBuilder();
		int prev = 0;
		for(int i = s.indexOf('\''); i >=0 ; i = s.indexOf('\'',prev)){			
			bld.append(s.substring(prev, i).replace(' ', '_'));
			prev = s.indexOf('\'', i+1);
			if(prev<0){
				prev=i;
				break;
			}
			else{
				prev++;
				bld.append(s.substring(i,prev));
			}
		}
		bld.append(s.substring(prev, s.length()).replace(' ', '_'));
		String result = bld.toString();
		return result;
	}

	private Object adjustValue(Object value, IProperty prop) {
		
		if (value instanceof Number) {
			Number n = (Number) value;
			if (n.doubleValue() == 0.0) {
				return value;
			}
			String unitKindName = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_UNIT_KIND_LABEL,String.class, null);
			if (unitKindName != null) {
				UnitKind unitKind = UnitKind.valueOf(unitKindName);
				if (unitKind != null) {
					Unit unit = Units.getPrimaryUnit(unitKind);
					return new ParsedDimension(n.doubleValue(), unit,"InferTransformer");
				}
			}
		}
		return value;
	}

	@Override
	public String getId() {
		return ID;
	}
	
	private static class OwnExpressionEnvironment implements IExpressionEnvironment
	{
		private static final long serialVersionUID = 7548075608017313899L;

		public OwnExpressionEnvironment(Object object, BasicUniverseProperty bm) {
			super();
			this.object = object;
			this.bm = bm;
		}

		private final Object object;
		
		private final BasicUniverseProperty bm;
		
		@Override
		public IClassResolver getClassResolver() {
			return null;
		}

		@Override
		public IListenableExpression<?> getBinding(
				String path) {
			if (path.equals("this")) {
				return new ConstantExpression(object);
			}

			IProperty property = bm.getUniverse().getProperty(object, path);
			if (property == null) {
				property = bm.getUniverse().getProperty(object,	path.replace('_',' '));
			}

			if (property != null) {
				Object value2 = property.getValue(object);

				if (value2 instanceof DescribedValue) {
					DescribedValue dv = (DescribedValue) value2;
					Object value3 = dv.value();
					if (value3 instanceof List) {
						List vl = (List) value3;
						value3 = vl.get(0);
					}
					while (value3 instanceof IHasValue) {
						IHasValue vl = (IHasValue) value3;						
						if (value3 instanceof ParsedDimension) {
							ParsedDimension m = (ParsedDimension) value3;
							value2 = m.value();
							break;
						}
						if (value3 instanceof ParsedValueSequence) {
							ParsedValueSequence m = (ParsedValueSequence) value3;
							value2 = m.getValueAsString();
							break;
						}
						if (value3 instanceof ParsedIdent) {
							value2 = value3;
							break;
						}
						value2 = vl.value();
						value3 = value2;
					}
					return new ConstantExpression(value2);
				}
			}
			return null;
		}
	}

}
